package com.spring.arch.common.jpa.repository;

import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.Getter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.HashMap;
import java.util.Map;

/**
 * 抽象的自定义查询持久类
 */
public abstract class AbstractCustomizedRepository {

    private Map<EntityPath<?>, PathBuilder<?>> pathBuilderMap = new HashMap<>();

    protected <T> JPAQuery<T> createQuery(JPAQueryFactory queryFactory, EntityPath<?> entityPath, Expression<T> expression) {
        return queryFactory
                .select(expression)
                .from(entityPath);
    }

    protected void applyPaging(EntityPath<?> entityPath, JPAQuery<?> jpaQuery, Pageable pageable) {
        jpaQuery
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize());
        applySort(entityPath, jpaQuery, pageable.getSort());
    }

    protected void applySort(EntityPath<?> entityPath, JPAQuery<?> jpaQuery, Sort sort) {
        PathBuilder<?> builder = getPathBuilder(entityPath);

        for (Sort.Order order : sort) {
            jpaQuery.orderBy(toOrderSpecifier(builder, order));
        }
    }

    private OrderSpecifier<?> toOrderSpecifier(PathBuilder<?> builder, Sort.Order order) {
        return new OrderSpecifier(
                order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC,
                buildOrderPropertyPathFrom(builder, order), toQueryDslNullHandling(order.getNullHandling())
        );
    }

    private Expression<?> buildOrderPropertyPathFrom(PathBuilder<?> builder, Sort.Order order) {

        Assert.notNull(order, "Order must not be null!");

        PropertyPath path = PropertyPath.from(order.getProperty(), builder.getType());
        Expression<?> sortPropertyExpression = builder;

        while (path != null) {

            if (!path.hasNext() && order.isIgnoreCase()) {
                // if order is ignore-case we have to treat the last path segment as a String.
                sortPropertyExpression = Expressions.stringPath((Path<?>) sortPropertyExpression, path.getSegment()).lower();
            } else {
                sortPropertyExpression = Expressions.path(path.getType(), (Path<?>) sortPropertyExpression, path.getSegment());
            }

            path = path.next();
        }

        return sortPropertyExpression;
    }

    private OrderSpecifier.NullHandling toQueryDslNullHandling(Sort.NullHandling nullHandling) {

        Assert.notNull(nullHandling, "NullHandling must not be null!");

        switch (nullHandling) {

            case NULLS_FIRST:
                return OrderSpecifier.NullHandling.NullsFirst;

            case NULLS_LAST:
                return OrderSpecifier.NullHandling.NullsLast;

            case NATIVE:
            default:
                return OrderSpecifier.NullHandling.Default;
        }
    }

    private PathBuilder<?> getPathBuilder(EntityPath<?> entityPath) {
        PathBuilder<?> pathBuilder = pathBuilderMap.get(entityPath);
        if (pathBuilder == null) {
            pathBuilder = new PathBuilder<>(entityPath.getType(), entityPath.getMetadata());
            pathBuilderMap.put(entityPath, pathBuilder);
        }

        return pathBuilder;
    }
}